﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Deployment.Compression;
using Microsoft.Deployment.Compression.Cab;

namespace Noris.Tools.DbManager.Data
{
    internal class Cabinet : IDisposable
    {
        internal Cabinet()
        {
            this.Cabinets = new Dictionary<string, CabinetInfo>();
        }
        public void Dispose()
        {
            foreach (CabinetInfo cabinetInfo in this.Cabinets.Values)
                cabinetInfo.Dispose();
            this.Cabinets.Clear();
        }
        protected Dictionary<string, CabinetInfo> Cabinets { get; set; }

        internal CabinetInfo SelectCabinetFile(string fileName)
        {
            CabinetInfo cabinetInfo;
            if (!this.Cabinets.TryGetValue(fileName, out cabinetInfo))
            {
                cabinetInfo = new CabinetInfo(this.Cabinets.Count, new FileInfo(fileName));
                this.Cabinets.Add(fileName, cabinetInfo);
            }
            return cabinetInfo;
        }
    }
    internal class CabinetInfo : IDisposable
    {
        internal CabinetInfo(int cabIndex, System.IO.FileInfo cabFileInfo)
        {
            this.CabIndex = cabIndex;
            this.CabFileInfo = cabFileInfo;
            string tempDir = Path.Combine(Path.Combine(Path.GetTempPath(), "Unpack"),"Cab~" + cabIndex.ToString("000000"));
            this.TempDirInfo = new DirectoryInfo(tempDir);
            this.InnerDirectories = new Dictionary<string, string>();
        }
        public void Dispose()
        {
            try
            {
                this.TempDirInfo.Refresh();
                if (this.TempDirInfo.Exists)
                    this.TempDirInfo.Delete(true);
            }
            catch (Exception) { }
        }
        /// <summary>
        /// Index = specifikuje číslo TEMP adresáře
        /// </summary>
        protected int CabIndex { get; private set; }
        /// <summary>
        /// CAB soubor
        /// </summary>
        internal System.IO.FileInfo CabFileInfo { get; private set; }
        /// <summary>
        /// TEMP adresář pro rozbalování
        /// </summary>
        internal System.IO.DirectoryInfo TempDirInfo { get; private set; }
        /// <summary>
        /// CAB soubor existuje?
        /// </summary>
        internal bool Exists { get { return this.CabFileInfo.Exists; } }
        /// <summary>
        /// Vnitřní CAB soubory a adresáře, kam jsou rozbaleny
        /// </summary>
        protected Dictionary<string, string> InnerDirectories { get; set; }
        internal IList<CabFileInfo> GetFiles()
        {
            CabInfo cabInfo = new CabInfo(this.CabFileInfo.FullName);
            return cabInfo.GetFiles();
        }
        /// <summary>
        /// Metoda vrátí data o jednom souboru v Cabinetu
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        internal CabFileInfo GetFile(string fileName)
        {
            CabInfo cabInfo = new CabInfo(this.CabFileInfo.FullName);
            return cabInfo.GetFiles().FirstOrDefault(c => String.Equals(c.Name, fileName, StringComparison.InvariantCultureIgnoreCase));
        }
        /// <summary>
        /// Metoda rozbalí jeden z vnitřních souborů CAB v tomto Cabinetu do svého temp podadresáře, a vrátí soupis souborů.
        /// </summary>
        /// <param name="cabFile"></param>
        /// <returns></returns>
        internal List<FileSystemInfo> GetInnerFiles(string cabFile)
        {
            return this.GetInnerFiles(cabFile, null);
        }
        /// <summary>
        /// Metoda rozbalí jeden z vnitřních souborů CAB v tomto Cabinetu do svého temp podadresáře, a vrátí soupis souborů.
        /// </summary>
        /// <param name="cabFile"></param>
        /// <returns></returns>
        internal List<FileSystemInfo> GetInnerFiles(string cabFile, EventHandler<ArchiveProgressEventArgs> progressHandler)
        {
            string cabDirs;
            if (!this.InnerDirectories.TryGetValue(cabFile, out cabDirs))
            {
                CabInfo cabInfo = new CabInfo(this.CabFileInfo.FullName);
                CabFileInfo cabFileInfo = cabInfo.GetFiles().FirstOrDefault(c => String.Equals(c.Name, cabFile, StringComparison.InvariantCultureIgnoreCase));
                if (cabFileInfo != null)
                {
                    cabDirs = Path.Combine(this.TempDirInfo.FullName, "Cab~" + this.InnerDirectories.Count.ToString("0000"));
                    Directory.CreateDirectory(cabDirs);
                    this.UnpackInnerFiles(cabInfo, cabFileInfo, cabDirs, progressHandler);
                }
                else
                {
                    cabDirs = null;
                }
                this.InnerDirectories.Add(cabFile, cabDirs);
            }
            if (cabDirs == null) return new List<FileSystemInfo>();
            DirectoryInfo dirinfo = new DirectoryInfo(cabDirs);
            if (!dirinfo.Exists) return new List<FileSystemInfo>();
            return new List<FileSystemInfo>(dirinfo.GetFileSystemInfos("*.*", SearchOption.AllDirectories));
        }

        private void UnpackInnerFiles(CabInfo cabInfo, CabFileInfo cabFileInfo, string cabDirs, EventHandler<ArchiveProgressEventArgs> progressHandler)
        {
            string destPath = Path.GetDirectoryName(cabDirs);
            string destName = cabFileInfo.Name;
            string destFile = Path.Combine(destPath, destName);

            if (progressHandler == null)
                cabInfo.UnpackFile(cabFileInfo.Name, destFile);
            else
            {
                List<string> files = new List<string>() { cabFileInfo.Name };
                List<string> destn = new List<string>() { destName };
                cabInfo.UnpackFiles(files, destPath, destn, progressHandler);
            }

            if (!File.Exists(destFile)) return;
            if (!String.Equals(Path.GetExtension(cabFileInfo.Name), ".cab", StringComparison.InvariantCultureIgnoreCase)) return;

            CabInfo innerCabInfo = new CabInfo(destFile);
            if (progressHandler == null)
                innerCabInfo.Unpack(cabDirs);
            else
                innerCabInfo.Unpack(cabDirs, progressHandler);
        }
    }
}
